using System;
using System.Windows.Threading;
using churchtool.Plugin.SermonUpload.ViewModel;
using System.Windows;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.IO;
using System.Data;
using churchtool.Plugin.SermonUpload.Data;


namespace churchtool.Plugin.SermonUpload.Operations.Services
{
    public static class UploadService
    {
        /* This service is nearly identical to ServiceTwo. The only difference in the
         * two classes is that this class takes three times as long to perform its
         * work as ServiceTwo. */

        /// <summary>
        /// Performs a service provided by this class
        /// </summary>
        /// <param name="workList">A list of work items (typically, file paths) to be processed.</param>
        /// <param name="viewModel">The Progress dialog view model for this application.</param>
        public static void DoWork(ProgressDialogViewModel viewModel)
        {
            var connection = FtpConnectionSettings.GetInstance();
            int filecount = connection.SourceFile.Count();
            int counter = 0;
            foreach (string file in connection.SourceFile)
            {
                counter++;

                // set up the host string to request.  this includes the target folder and the target file name (based on the source filename)
                string UploadPath = String.Format("{0}/{1}{2}", connection.Host, connection.TargetFolder == "" ? "" : connection.TargetFolder + "/", System.IO.Path.GetFileName(file));

                if (!UploadPath.ToLower().StartsWith("ftp://"))
                    UploadPath = "ftp://" + UploadPath;

                FtpWebRequest request = (FtpWebRequest)WebRequest.Create(UploadPath);

                request.UseBinary = true;
                request.UsePassive = connection.Passive;
                request.Method = WebRequestMethods.Ftp.UploadFile;
                request.Credentials = new NetworkCredential(connection.Username, connection.Password);
                //ToDO: upload through proxy is not implemented!
                // request.Proxy = new WebProxy(); 

                // Copy the contents of the file to the request stream.
                long FileSize = new FileInfo(file).Length;
                string FileSizeDescription = GetFileSize(FileSize); // e.g. "2.4 Gb" instead of 240000000000000 bytes etc...			
                int ChunkSize = 4096, NumRetries = 0, MaxRetries = 50;
                long SentBytes = 0;
                byte[] Buffer = new byte[ChunkSize];    // this buffer stores each chunk, for sending to the web service via MTOM

                try
                {
                    using (Stream requestStream = request.GetRequestStream())
                    {
                        using (FileStream fs = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                        {
                            int BytesRead = fs.Read(Buffer, 0, ChunkSize);	// read the first chunk in the buffer

                            // send the chunks to the web service one by one, until FileStream.Read() returns 0, meaning the entire file has been read.
                            while (BytesRead > 0)
                            {

                                try
                                {
                                    // send this chunk to the server.  it is sent as a byte[] parameter, but the client and server have been configured to encode byte[] using MTOM. 
                                    requestStream.Write(Buffer, 0, BytesRead);

                                    // sentBytes is only updated AFTER a successful send of the bytes. so it would be possible to build in 'retry' code, to resume the upload from the current SentBytes position if AppendChunk fails.
                                    SentBytes += BytesRead;

                                    // update the user interface
                                    string SummaryText = String.Format("Transferred {0} / {1}", GetFileSize(SentBytes), FileSizeDescription);
                                    if (filecount > 1)
                                        SummaryText = String.Format("Transfering file {0} of {1} - Transferred {2} / {3}", counter, filecount, GetFileSize(SentBytes), FileSizeDescription);

                                    var IncrementProgressCounter = new Action<int, string>(viewModel.IncrementProgressCounter);

                                    int n = Convert.ToInt32(((decimal)SentBytes / (decimal)FileSize) * 100);
                                    Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, IncrementProgressCounter, n, SummaryText);
                                    // bw.ReportProgress((int)(((decimal)SentBytes / (decimal)FileSize) * 100), SummaryText);
                                }
                                catch (Exception ex)
                                {
                                    if (NumRetries++ < MaxRetries)
                                    {
                                        // rewind the filestream and keep trying
                                        fs.Position -= BytesRead;
                                    }
                                    else
                                    {
                                        throw new Exception("Error occurred during upload, too many retries", ex);
                                    }
                                }
                                BytesRead = fs.Read(Buffer, 0, ChunkSize);	// read the next chunk (if it exists) into the buffer.  the while loop will terminate if there is nothing left to read
                            }
                        }
                    }
                }
                catch (WebException e)
                {
                    MessageBox.Show(e.Message, "Upload Error", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        #region Helpers

        /// <summary>
        /// Returns a description of a number of bytes, in appropriate units.
        /// e.g. 
        ///		passing in 1024 will return a string "1 Kb"
        ///		passing in 1230000 will return "1.23 Mb"
        /// Megabytes and Gigabytes are formatted to 2 decimal places.
        /// Kilobytes are rounded to whole numbers.
        /// If the rounding results in 0 Kb, "1 Kb" is returned, because Windows behaves like this also.
        /// </summary>
        public static string GetFileSize(long numBytes)
        {
            string fileSize = "";

            if (numBytes > 1073741824)
                fileSize = String.Format("{0:0.00} Gb", (double)numBytes / 1073741824);
            else if (numBytes > 1048576)
                fileSize = String.Format("{0:0.00} Mb", (double)numBytes / 1048576);
            else
                fileSize = String.Format("{0:0} Kb", (double)numBytes / 1024);

            if (fileSize == "0 Kb")
                fileSize = "1 Kb";	// min.							
            return fileSize;
        }



        #endregion
    }
}